/*
 *
 *   JPosition
 *   Copyright (C) 2008  Blas Orlando Soto Muñoz
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License and GNU General Public License
 *   for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   and GNU Lesser General Public License along with this program.
 *   If not, see <http://www.gnu.org/licenses/>. 
 * 
 */



package modelo.mapeado;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Vector;


/**
 * Esta Clase se utiliza para definir y obtener coordenadas geográficas, y poder representarla sobre los
 * Mapas, esta clase puede obtener coordenadas geográficas a trabes de direcciones así como hacer algunos
 * cálculos básicos relacionados con coordenadas.
 * @author Blas Orlando Soto Muñoz
 */
public class Coordinate {

    private double latitude = 0;
    private double longitude = 0;

    /**
     * Construye una instancia de Coordinate
     */
    public Coordinate() {
    }

    /**
     * Construye una instancia de Coordinate, a la cual se le puede espesificar latitud y longitud
     * @param latitude El valor de la Latitud
     * @param longitude El valor de la Longitud
     * @throws modelo.mapeado.CoordinateRangeException Excepción en caso de sobrepasar los rangos maxixmos de latitud y longitud
     */
    public Coordinate(double latitude, double longitude) throws CoordinateRangeException  {
        
        this.setLatitude(latitude);
        this.setLongitude(longitude);
    }

    /**
     * Construye una instancia de Coordinate, a la cual se le puede especificar una Dirección y la Key de GoogleMaps
     * @param Address La dirección que especifica las coordenadas
     * @param Key La key de GoogleMaps
     * @throws java.io.IOException Excepción al producirse un error de conexión a la Web
     */
    public Coordinate(String address) throws IOException {
    	
        getStringCoordinate(address);
    }
    
    /**
     * Retorna la Latitud de la Coordenada
     * @return Latitud de la Coordenada
     */
    public double getLatitude() {
    	
        return latitude;
    }

    /**
     * Setea la Latitud de la Coordenada
     * @param val Latitud de la Coordenada
     * @throws modelo.mapeado.CoordinateRangeException Excepción en caso de sobrepasar los rangos maxixmos de latitud y longitud
     */
    public void setLatitude(double val) throws CoordinateRangeException {
    	
        if(val < -90 || val > 90)
            throw new CoordinateRangeException("Coordinates out of range");
        else
            this.latitude = val;
    }

    /**
     * Retorna la Longitud de la Coordenada
     * @return Longitud de la Coordenada
     */
    public double getLongitude() {
    	
        return longitude;
    }

    /**
     * Setea la Longitud de la Coordenada
     * @param val Longitud de la Coordenada
     * @throws modelo.mapeado.CoordinateRangeException Excepción en caso de sobrepasar los rangos maxixmos de latitud y longitud
     */
    public void setLongitude(double val) throws CoordinateRangeException {
    	
        if(val < -180 || val > 180)
            throw new CoordinateRangeException("Coordinates out of range");
        else
            this.longitude = val;
    }

    /**
     * Setea la Coordenada según una Dirección
     * @param Address La Dirección
     * @param Key La Key de GoogleMaps
     * @throws java.io.IOException
     */
    public void setValuesForAddress(String address) throws IOException {
    	
        getStringCoordinate(address);
    }
    
    /**
     * Retorna la distancia en mm (Milímetros) entre esta Coordenada y otra pasada por referencia
     * @param cor La Coordenada pasada por referencia
     * @return La Distancia en mm (Milímetros) entre esta Coordenada y la pasada por referencia
     */
    public double getDistance(Coordinate cor) {
        
        double a=this.getLatitude()-cor.getLatitude();
        double b=this.getLongitude()-cor.getLongitude();
        
        return Math.hypot(a, b)*111.195*1E6; 
    }
    
    /**
     * Retorna la distancia en mm (Milímetros) entre dos Coordenadas pasadas por referencia
     * @param corA Una Coordenada
     * @param corB Una Coordenada
     * @return La Distancia en mm (Milímetros) entre dos Coordenadas
     */
    public static double getDistance(Coordinate corA,Coordinate corB) {
    	
        double a=corA.getLatitude()-corB.getLatitude();
        double b=corA.getLongitude()-corB.getLongitude();
        
        return Math.hypot(a, b)*111.195*1E6;
    }
    
    /**
     * Retorna la distancia en mm (Milimetros) desde una lista de Coordenadas
     * @param lista La lista de Coordenadas
     * @return La Distancia en mm (Milimetros) de toas las Coordenadas de la lista
     */
    public static double getDistance(ArrayList<Coordinate> lista) {
    	
        double counter = 0;
        boolean first = true;
        Coordinate one = null;
        for(Coordinate coor : lista) {
            if(first) {
                one = coor;
                first = false;
            }
            else {
                counter += one.getDistance(coor);
                one = coor;
            }
        }
        return counter;
    }
   
    private void getStringCoordinate(String address) throws IOException {
    	
        URL url = new URL(getGeoCoordinateURL(address));
        InputStream stream = url.openStream();
        BufferedReader buffer = new BufferedReader(new InputStreamReader(stream));
        String web = buffer.readLine();

        String coordenadas[] = Split(web, ',');
        this.latitude=Double.parseDouble(coordenadas[2]);
        this.longitude=Double.parseDouble(coordenadas[3]);
    }

    private String getGeoCoordinateURL(String address) {
    	
        return "http://maps.google.es/maps/geo?q=" + UrlEncode(address) +
                "&output=csv";
    }

    private String UrlEncode(String url) {
    	
        StringBuffer buffer = new StringBuffer();
        char word;
        for (int i = 0; i < url.length(); i++) {
            word = url.charAt(i);
            if ((word >= '0' && word <= '9') ||
                    (word >= 'A' && word <= 'Z') ||
                    (word >= 'a' && word <= 'z'))
                buffer.append(word);
            else
                buffer.append("%").append(Integer.toHexString((int) url.charAt(i)));
        }
        return buffer.toString();
    }

    private String[] Split(String text, int character) {
    	
        Vector<String> vector = new Vector<String>();
        int current = 0;
        int prior = 0;

        while ((current = text.indexOf(character, prior)) >= 0) {
            vector.addElement(text.substring(prior, current));
            prior = current + 1;
        }

        vector.addElement(text.substring(prior));
        String[] split = new String[vector.size()];
        vector.copyInto(split);

        return split;
    }    
    
    public String toString() {
    	
    	return latitude+","+longitude;
    }
}